﻿// <copyright file="InternalCache.cs" company="Lu Studio">
// Copyright (c) Lu Studio. All rights reserved.
// </copyright>
// <author>lu qi</author>
// <createTime>2021-06-19</createTime>
namespace ElfKit.Common
{
    using System;
    using System.Runtime.Caching;

    /// <summary>
    /// 内部缓存.
    /// </summary>
    public static class InternalCache
    {
        #region 定义

        /// <summary>
        /// 默认30分钟.
        /// </summary>
        public const int DefaultExpirationSeconds = 60 * 30;

        /// <summary>
        ///  缓存实例.
        /// </summary>
        private static MemoryCache cache = MemoryCache.Default;

        #endregion

        #region Get

        /// <summary>
        /// 获取缓存.
        /// </summary>
        /// <typeparam name="T">类型.</typeparam>
        /// <param name="key">缓存的键</param>
        /// <returns><typeparamref name="T"/>值.</returns>
        /// <exception cref="ArgumentException"><paramref name="key"/>is null or string.Empty or WhiteSpace.</exception>
        public static T Get<T>(string key)
        {
            Guard.NotNullOrWhiteSpace(key, nameof(key));

            return (T)cache.Get(key);
        }

        /// <summary>
        /// 获取缓存.
        /// </summary>
        /// <param name="key">缓存的键</param>
        /// <returns>值.</returns>
        /// <exception cref="ArgumentException"><paramref name="key"/>is null or string.Empty or WhiteSpace.</exception>
        public static object Get(string key)
        {
            Guard.NotNullOrWhiteSpace(key, nameof(key));

            return cache.Get(key);
        }

        /// <summary>
        /// 获得<paramref name="keyFactory"/>产生的键对应的<typeparamref name="T"/>类型的值.
        /// 如果没有从缓存中获得则使用<paramref name="valueFactory"/>产生值并以<paramref name="valueFactory"/>产生的键设置到缓存中.
        /// </summary>
        /// <param name="keyFactory">键工厂委托.</param>
        /// <param name="valueFactory">值工厂委托.</param>
        /// <param name="expiration">过期时间</param>
        /// <returns><typeparamref name="T"/>实例.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="keyFactory"/>is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="valueFactory"/>is null.</exception>
        public static T Get<T>(Func<string> keyFactory, Func<T> valueFactory, int expiration = DefaultExpirationSeconds)
        {
            return HandleGetByFactory(keyFactory, valueFactory, (key, value) => Set(key, value, expiration));
        }

        /// <summary>
        /// 获得<paramref name="keyFactory"/>产生的键对应的<typeparamref name="T"/>类型的值.
        /// 如果没有从缓存中获得则使用<paramref name="valueFactory"/>产生值并以<paramref name="valueFactory"/>产生的键设置到缓存中.
        /// </summary>
        /// <param name="keyFactory">键工厂委托.</param>
        /// <param name="valueFactory">值工厂委托.</param>
        /// <param name="policy"><see cref="CacheItemPolicy"/>策略.</param>
        /// <returns><typeparamref name="T"/>实例.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="keyFactory"/>is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="valueFactory"/>is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="policy"/>is null.</exception>
        public static T Get<T>(Func<string> keyFactory, Func<T> valueFactory, CacheItemPolicy policy)
        {
            return HandleGetByFactory(keyFactory, valueFactory, (key, value) => Set(key, value, policy));
        }

        #endregion

        #region  Set

        /// <summary>
        /// 设置缓存.
        /// </summary>
        /// <param name="key">缓存的键.</param>
        /// <param name="value">需要缓存的值.</param>
        /// <param name="expiration">过期时间</param>
        /// <exception cref="ArgumentNullException"><paramref name="value"/>is null.</exception>
        /// <exception cref="ArgumentException"><paramref name="key"/>is null or string.Empty or WhiteSpace.</exception>
        public static void Set(
            string key,
            object value,
            int expiration = DefaultExpirationSeconds)
        {
            Set(key, value, Policy.Default(expiration));
        }

        /// <summary>
        /// 设置缓存.
        /// </summary>
        /// <param name="key">缓存的键.</param>
        /// <param name="value">需要缓存的值.</param>
        /// <param name="policy"><see cref="CacheItemPolicy"/>策略.</param>
        /// <exception cref="ArgumentNullException"><paramref name="policy"/>is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="value"/>is null.</exception>
        /// <exception cref="ArgumentException"><paramref name="key"/>is null or string.Empty or WhiteSpace.</exception>
        public static void Set(
            string key,
            object value,
            CacheItemPolicy policy)
        {
            Guard.NotNullOrWhiteSpace(key, nameof(key));
            Guard.NotNull(value, nameof(value));
            Guard.NotNull(policy, nameof(policy));

            cache.Set(key, value, policy);
        }

        #endregion

        #region Remove

        /// <summary>
        /// 从缓存中删除<paramref name="key"/>对应的值.
        /// </summary>
        /// <param name="key">缓存的键</param>
        /// <returns>删除的值.</returns>
        /// <exception cref="ArgumentException"><paramref name="key"/>is null or string.Empty or WhiteSpace.</exception>
        public static object Remove(string key)
        {
            Guard.NotNullOrWhiteSpace(key, nameof(key));

            return cache.Remove(key);
        }

        /// <summary>
        /// 从缓存中删除<paramref name="key"/>对应的值.
        /// </summary>
        /// <typeparam name="T">类型.</typeparam>
        /// <param name="key">缓存的键</param>
        /// <returns>删除的值.</returns>
        /// <exception cref="ArgumentException"><paramref name="key"/>is null or string.Empty or WhiteSpace.</exception>
        public static T Remove<T>(string key)
        {
            object value = Remove(key);

            return value is null ? default(T) : (T)value;
        }

        #endregion

        #region Policy

        /// <summary>
        /// 缓存策略.
        /// </summary>
        public static class Policy
        {
            /// <summary>
            /// 获得默认的<see cref="CacheItemPolicy"/>实例.
            /// </summary>
            /// <param name="expiration">过期时间</param>
            /// <exception cref="ArgumentException"><paramref name="expiration"/>is less than or equal zero.</exception>
            public static CacheItemPolicy Default(int expiration = DefaultExpirationSeconds)
            {
                return new CacheItemPolicy
                {
                    AbsoluteExpiration = DateTime.Now.AddSeconds(expiration)
                };
            }

            /// <summary>
            /// 获得永久的<see cref="CacheItemPolicy"/>实例.
            /// </summary>
            public static CacheItemPolicy Permanent()
            {
                return new CacheItemPolicy
                {
                    AbsoluteExpiration = DateTime.MaxValue
                };
            }
        }

        #endregion

        #region GetKey

        /// <summary>
        /// 获得<paramref name="type"/>类型全名与<paramref name="memberName"/>的键.
        /// </summary>
        /// <param name="type"><see cref="Type"/>类型.</param>
        /// <param name="memberName">方法名.</param>
        /// <returns>键.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="type"/>is null.</exception>
        /// <exception cref="ArgumentException"><paramref name="memberName"/>is null or string.Empty or WhiteSpace.</exception>
        public static string GetKey(Type type, string memberName)
        {
            Guard.NotNull(type, nameof(type));
            Guard.NotNullOrWhiteSpace(memberName, nameof(memberName));

            return string.Concat(type.FullName, Constant.Strings.Pipe, memberName);
        }

        #endregion

        #region 内部方法

        /// <summary>
        /// 处理获得<paramref name="keyFactory"/>产生的键对应的<typeparamref name="T"/>类型的值.
        /// 如果没有从缓存中获得则使用<paramref name="valueFactory"/>产生值并以<paramref name="valueFactory"/>产生的键设置到缓存中.
        /// </summary>
        /// <param name="keyFactory">键工厂委托.</param>
        /// <param name="valueFactory">值工厂委托.</param>
        /// <param name="setter">设置缓存委托.</param>
        /// <returns><typeparamref name="T"/>实例.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="keyFactory"/>is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="valueFactory"/>is null.</exception>
        private static T HandleGetByFactory<T>(Func<string> keyFactory, Func<T> valueFactory, Action<string, T> setter)
        {
            Guard.NotNull(keyFactory, nameof(keyFactory));
            Guard.NotNull(valueFactory, nameof(valueFactory));

            string key = keyFactory();
            object value = Get(key);
            if (value == null)
            {
                T workValue = valueFactory();
                setter(key, workValue);
                return workValue;
            }

            return (T)value;
        }

        #endregion
    }
}